Skip to content

[OMNIML-5613] Quantize ResNet residual adds in torch ONNX example - #2024

Open
ajrasane wants to merge 15 commits into
mainfrom
ajrasane/quantize-resnet-residual-adds
Open

[OMNIML-5613] Quantize ResNet residual adds in torch ONNX example#2024
ajrasane wants to merge 15 commits into
mainfrom
ajrasane/quantize-resnet-residual-adds

Conversation

@ajrasane

@ajrasane ajrasane commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Type of change: Bug fix

Adds recipe-backed FP8 and INT8 residual quantization for timm ResNet models in the torch ONNX example:

  • Adds FP8 and INT8 PTQ recipes that enable a shortcut quantizer immediately before each residual Add.
  • Inserts shortcut quantizers after ModelOpt module conversion so recipes configure and calibrate them in the normal quantization pass.
  • Adds --recipe support for PTQ and AutoQuantize recipes and renames --quantize_mode to --qformat.
  • Verifies all 16 ResNet-50 residual additions have shortcut Q/DQ immediately before the Add.

ResNet support scope

ResNet and other convolutional architectures are supported only with FP8 and INT8. AutoQuantize, MXFP8, NVFP4, and INT4_AWQ are not supported for ResNet because TensorRT has limited convolution kernel support. Transformer architectures containing individual Conv2d layers continue to use format-specific Conv overrides.

Usage

python examples/torch_onnx/torch_quant_to_onnx.py \
    --timm_model_name=resnet50 \
    --recipe=timm/resnet/ptq/fp8 \
    --onnx_save_path=resnet50.onnx

Use timm/resnet/ptq/int8 for INT8. Without --recipe, --qformat selects a built-in quantization preset.

Testing

  • All configured pre-commit hooks passed, including recipe schema and license validation.
  • Focused AutoQuantize recipe mapping regression passed.
  • FP8/INT8 recipe export coverage verifies all 16 ResNet-50 shortcut Q/DQ pairs.
  • TensorRT engine builds passed for FP8 and INT8 on Ada.

Before your PR is "Ready for review"

  • Is this change backward compatible?: ✅
  • If you copied code from any other sources or added a new PIP dependency, did you follow guidance in CONTRIBUTING.md: N/A
  • Did you write any new necessary tests?: ✅
  • Did you update the changelog?: ✅

🤖 Generated by Codex (AI agent).

ajrasane added 2 commits July 28, 2026 17:24
Signed-off-by: ajrasane <131806219+ajrasane@users.noreply.github.com>
Signed-off-by: ajrasane <131806219+ajrasane@users.noreply.github.com>
@copy-pr-bot

copy-pr-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds timm ResNet quantization plugins and PTQ recipes, integrates recipe selection into the Torch ONNX example, extends AutoQuantize matching and cost handling, and updates ONNX FP8/INT8 FP16 transformations with structural validation.

Changes

timm ResNet quantization and export

Layer / File(s) Summary
timm ResNet quantization plugin
modelopt/torch/quantization/plugins/*
Supported ResNet modules are rewritten with quantization-aware marker classes, residual and output hooks, and AutoQuantize grouping/scoring rules.
ResNet recipes and example wiring
modelopt_recipes/timm/resnet/ptq/*, examples/torch_onnx/torch_quant_to_onnx.py, modelopt_recipes/README.md, examples/torch_onnx/README.md, CHANGELOG.rst
PTQ recipes for FP8, INT8, MXFP8, NVFP4, and AWQ-lite are added; the ONNX example selects model-specific recipes and defaults; documentation describes the new recipe layout and quantizer placement.
AutoQuantize matching and cost reconstruction
modelopt/torch/quantization/algorithms.py, modelopt/torch/quantization/conversion.py, modelopt/torch/opt/dynamic.py
Quantizer attributes, parent classes, selected-format compression, and replay matching are persisted and evaluated for quantizable modules and registered subclasses.
ONNX conversion and graph validation
modelopt/onnx/*, modelopt/torch/_deploy/utils/torch_onnx.py, tests/examples/torch_onnx/*, tests/unit/onnx/*
FP8 RGB input-stem handling and INT8 FP16 Q/DQ conversion are added, with tests covering graph structure, dtype checks, opsets, and invalid scales.

Estimated code review effort: 4 (Complex) | ~60 minutes

Suggested reviewers: vishalpandya1990, cjluo-nv

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Security Anti-Patterns ✅ Passed No forbidden patterns found in the full PR diff; changed Python files had no unsafe torch/numpy loads, trust_remote_code, builtin eval/exec, or nosec, and no dependency manifests changed.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: quantizing ResNet residual additions in the torch ONNX example.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ajrasane/quantize-resnet-residual-adds

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.82%. Comparing base (99116c3) to head (8b7079b).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2024      +/-   ##
==========================================
- Coverage   78.60%   76.82%   -1.78%     
==========================================
  Files         522      523       +1     
  Lines       60167    62598    +2431     
==========================================
+ Hits        47294    48094     +800     
- Misses      12873    14504    +1631     
Flag Coverage Δ
examples-diffusers 20.82% <ø> (?)
examples-gpt-oss 13.27% <ø> (?)
examples-hf_ptq 21.50% <ø> (?)
examples-llm_distill 13.34% <ø> (?)
examples-llm_eval 17.12% <ø> (?)
examples-llm_qat 16.65% <ø> (?)
examples-llm_sparsity 15.93% <ø> (?)
examples-megatron_bridge 25.70% <ø> (?)
examples-specdec_bench 13.02% <ø> (?)
examples-speculative_decoding 17.54% <ø> (?)
examples-torch_trt 15.10% <ø> (?)
gpu 58.65% <ø> (-0.62%) ⬇️
unit 55.30% <ø> (-0.10%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ajrasane
ajrasane marked this pull request as ready for review July 28, 2026 18:03
@ajrasane
ajrasane requested review from a team as code owners July 28, 2026 18:03
@ajrasane ajrasane self-assigned this Jul 28, 2026
Signed-off-by: ajrasane <131806219+ajrasane@users.noreply.github.com>

@cjluo-nv cjluo-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot review (bedrock-claude-opus-5) — DM the bot to share feedback.

Small, focused change (+90/-2) that adds per-block residual-add quantizers to the timm ResNet path of the torch→ONNX example, with CHANGELOG + README updates and an ONNX-level assertion in the existing example test. No licensing concerns; no prompt-injection content in the PR metadata. A few things worth addressing before merge:

  1. Duplicated calibration logic / extra data pass. _add_resnet_residual_quantizers re-implements the enable_calib → forward-loop → load_calib_amax dance that _calibrate_uncalibrated_quantizers (same file) already performs, and adds a second full pass over the calibration set for ResNet (the first happens inside quantize_model for the FP8 Conv overrides). If the residual quantizer were attached as input_quantizer on the activation module and created before quantize_model, both the calibration and the dead-quantizer guard would come for free from the existing helpers. Even more idiomatic: modelopt already supports this via QuantModuleRegistry.register({nn.ReLU: "nn.ReLU"})(QuantInputBase) (exactly what modelopt/torch/quantization/nn/modules/quant_activations.py does for nn.LeakyReLU) plus a {"parent_class": "nn.ReLU", "quantizer_name": "*input_quantizer"} config entry, which gets calibrated by mtq.quantize's forward loop and is visible to mtq.print_quant_summary / modelopt state. Please either reuse one of these paths or note in the PR/comment why the manual hook is needed.

  2. Residual quantizers bypass the file's own amax<=0/NaN guard, and load_calib_amax() is strict. _disable_dead_quantizers only inspects input_quantizer/output_quantizer/weight_quantizer, and it runs inside quantize_model — i.e. before these quantizers exist. A residual quantizer that calibrates to amax == 0 (or NaN) will therefore reach the FP8 exporter, which is precisely the scale = 448 / amax division the guard exists to prevent. Also, unlike _calibrate_uncalibrated_quantizers, this code calls load_calib_amax() without strict=False, so any block that didn't see data raises.

  3. auto mode picks the residual format from the search space, not the search result. After mtq.auto_quantize, each block's actual format is known (e.g. block.conv3.input_quantizer._num_bits); deriving num_bits from the union of requested formats can give an FP8 residual Q/DQ next to an INT8-quantized block (or the reverse), which is what the rest of this file goes to some length to avoid for TRT.

Minor: the two new functions are the only helpers in this file without docstrings; and assert len(residual_adds) == 16 asserts that every Add in the exported graph is a residual add, which will break confusingly if the exporter ever emits an unrelated Add — consider filtering to the 16 residual adds (e.g. by producer/consumer pattern) before the count assertion.

Comment thread examples/torch_onnx/torch_quant_to_onnx.py Outdated
Comment thread examples/torch_onnx/torch_quant_to_onnx.py Outdated
Comment thread examples/torch_onnx/torch_quant_to_onnx.py Outdated
Comment thread tests/examples/torch_onnx/test_torch_quant_to_onnx.py
Comment thread examples/torch_onnx/torch_quant_to_onnx.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/torch_onnx/torch_quant_to_onnx.py`:
- Around line 223-239: Update _add_resnet_residual_quantizers and the
surrounding auto-quantization flow so residual quantizers are installed and
configured before mtq.auto_quantize() runs. For auto mode, include these
residual quantizer modules in every candidate format configuration used by the
search, ensuring their forced INT8/FP8 precision is scored and counted toward
the effective-bits constraint while preserving the existing non-auto behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9cd948d5-f05e-4d18-9714-517039e16f2d

📥 Commits

Reviewing files that changed from the base of the PR and between 87c9f8c and c15755c.

📒 Files selected for processing (4)
  • CHANGELOG.rst
  • examples/torch_onnx/README.md
  • examples/torch_onnx/torch_quant_to_onnx.py
  • tests/examples/torch_onnx/test_torch_quant_to_onnx.py

Comment thread examples/torch_onnx/torch_quant_to_onnx.py Outdated
Signed-off-by: ajrasane <131806219+ajrasane@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
examples/torch_onnx/torch_quant_to_onnx.py (1)

636-642: ⚠️ Potential issue | 🟠 Major

Install residual quantizers before the quantization/search pass.

At Line 636, residual quantizers are added only after quantized_model has been created. In auto mode, they are therefore absent from candidate scoring and effective-bits constraints; the later heuristic can also choose a format different from the per-block format selected by AutoQuantize. The standard path additionally requires a second calibration pass and runs dead-quantizer cleanup before these modules exist.

Move installation/configuration before quantization, or explicitly integrate these quantizers into AutoQuantize and rerun cleanup after calibration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/torch_onnx/torch_quant_to_onnx.py` around lines 636 - 642, Move the
`_add_resnet_residual_quantizers` installation and configuration before the
quantization/search pass creates `quantized_model`, so residual quantizers
participate in AutoQuantize candidate scoring and effective-bits constraints.
Ensure calibration and dead-quantizer cleanup operate on these modules, and
remove the current post-quantization-only installation path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@examples/torch_onnx/torch_quant_to_onnx.py`:
- Around line 636-642: Move the `_add_resnet_residual_quantizers` installation
and configuration before the quantization/search pass creates `quantized_model`,
so residual quantizers participate in AutoQuantize candidate scoring and
effective-bits constraints. Ensure calibration and dead-quantizer cleanup
operate on these modules, and remove the current post-quantization-only
installation path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 0ba0d581-0d68-4e4d-993c-47c5819b72dd

📥 Commits

Reviewing files that changed from the base of the PR and between c15755c and d2a78de.

📒 Files selected for processing (4)
  • CHANGELOG.rst
  • examples/torch_onnx/README.md
  • examples/torch_onnx/torch_quant_to_onnx.py
  • tests/examples/torch_onnx/test_torch_quant_to_onnx.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • examples/torch_onnx/README.md
  • CHANGELOG.rst
  • tests/examples/torch_onnx/test_torch_quant_to_onnx.py

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: ajrasane <131806219+ajrasane@users.noreply.github.com>
@ajrasane
ajrasane requested a review from a team as a code owner July 29, 2026 16:04
@ajrasane
ajrasane requested a review from cjluo-nv July 29, 2026 16:04
@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://NVIDIA.github.io/Model-Optimizer/pr-preview/pr-2024/

Built to branch gh-pages at 2026-07-30 00:56 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 4

🧹 Nitpick comments (5)
tests/unit/onnx/test_fold_casts.py (1)

86-97: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant dtype plumbing in _initializer_q_model.

input_dtype and output_dtype are computed identically, and outputs[0] is built with FLOAT16 at Line 86 only to be overwritten at Line 97. Collapse into a single variable used at construction time.

♻️ Proposed simplification
-    outputs = [helper.make_tensor_value_info("y", TensorProto.FLOAT16, [None, 4])]
+    float_dtype = TensorProto.FLOAT16 if opset >= 19 else TensorProto.FLOAT
+    outputs = [helper.make_tensor_value_info("y", float_dtype, [None, 4])]
     if shared:
         nodes.append(helper.make_node("Identity", ["w"], ["w_out"], "identity"))
         outputs.append(helper.make_tensor_value_info("w_out", TensorProto.FLOAT, [4, 4]))
@@
-    input_dtype = TensorProto.FLOAT16 if opset >= 19 else TensorProto.FLOAT
-    output_dtype = TensorProto.FLOAT16 if opset >= 19 else TensorProto.FLOAT
-    outputs[0].type.tensor_type.elem_type = output_dtype
     return helper.make_model(
         helper.make_graph(
             nodes,
             "g",
-            [helper.make_tensor_value_info("x", input_dtype, [None, 4])],
+            [helper.make_tensor_value_info("x", float_dtype, [None, 4])],
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/onnx/test_fold_casts.py` around lines 86 - 97, In
`_initializer_q_model`, replace the redundant `input_dtype` and `output_dtype`
calculations with one shared dtype variable derived from `opset`, and use it
when constructing the primary `y` output instead of creating it as `FLOAT16` and
mutating it afterward. Remove the subsequent
`outputs[0].type.tensor_type.elem_type` assignment while preserving the existing
opset-dependent dtype.
examples/torch_onnx/torch_quant_to_onnx.py (1)

232-259: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

_calibrate_new_quantizers mutates _if_quant state without honoring pre-existing disabled-quant quantizers.

enabled_quantizers is filtered by is_enabled (i.e. _disabled), not by _if_quant. Any quantizer that was enabled but intentionally had disable_quant() applied earlier gets enable_quant() in the finally block, silently turning quantization back on. Filtering on module._if_quant instead would make the save/restore symmetric.

♻️ Suggested tweak
-    enabled_quantizers = [
-        module
-        for module in model.modules()
-        if isinstance(module, TensorQuantizer) and module.is_enabled
-    ]
+    enabled_quantizers = [
+        module
+        for module in model.modules()
+        if isinstance(module, TensorQuantizer) and module.is_enabled and module._if_quant
+    ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/torch_onnx/torch_quant_to_onnx.py` around lines 232 - 259, Update
_calibrate_new_quantizers to track quantizers based on their pre-existing
_if_quant state rather than is_enabled when building enabled_quantizers. Restore
quantization only for quantizers whose _if_quant state was originally active,
preserving intentionally disabled quantizers through the finally block.
tests/examples/torch_onnx/test_torch_quant_to_onnx.py (2)

35-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Unguarded producers[...] lookups will raise KeyError instead of a useful failure.

If an Add input is a graph input or an initializer (no producer node), Line 54/56 raises KeyError rather than an assertion explaining what the graph looks like. Same for the next(...) lookups at Lines 88 and 95, which raise StopIteration if the exporter ever emits MatMul/ReduceMean instead of Gemm/GlobalAveragePool. Prefer producers.get(...) plus explicit asserts so failures are diagnosable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/examples/torch_onnx/test_torch_quant_to_onnx.py` around lines 35 - 62,
Harden _assert_residual_adds_are_quantized against missing graph producers by
replacing direct producers[...] accesses with producers.get(...) and explicit
assertions that identify the missing input or node. Apply the same pattern to
the MatMul/ReduceMean lookup paths around the next(...) calls, asserting the
expected producer exists before dereferencing it while preserving the current
validation behavior.

64-116: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hard-coded node counts (54/52, 53, 12/4/4) need a comment explaining their derivation.

These numbers encode the exact ResNet-50 Q/DQ topology, but nothing in the test says where they come from, so a future exporter change produces an unexplainable assert 53 == 52. A one-line comment per assertion (e.g. "53 = 53 Conv weights, fc weight quantizer disabled") would make the failures actionable. As per path instructions, checked-in tests should "document expected behavior".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/examples/torch_onnx/test_torch_quant_to_onnx.py` around lines 64 - 116,
Add concise comments immediately before each hard-coded topology-count assertion
in the quantization test, explaining how the expected values derive from the
ResNet-50 Q/DQ structure and mode-specific behavior. Cover the activation
quantizer counts, DQ fanout counts, and int8 weight quantizer count, including
details such as the number of convolution weights and disabled fully connected
weight quantization; leave the assertions unchanged.

Source: Path instructions

modelopt/torch/_deploy/utils/torch_onnx.py (1)

48-48: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Importing a private helper across modules.

_convert_q_data_initializers_to_fp16 is underscore-private to modelopt/onnx/utils.py but is consumed here. Since it's now part of the export pipeline contract, consider promoting it to a public name (and adding it to that module's __all__) so the dependency is explicit.

Note the sequencing is load-bearing: this call raises if any Q scale is still FP32 after Line 670, which is why the stricter fold guard in modelopt/onnx/utils.py matters (flagged there).

Also applies to: 669-673

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modelopt/torch/_deploy/utils/torch_onnx.py` at line 48, Promote
_convert_q_data_initializers_to_fp16 in modelopt/onnx/utils.py to a public
helper name, add that name to the module’s __all__, and update the import and
call sites in the ONNX export flow around _convert_q_data_initializers_to_fp16
accordingly. Preserve the existing sequencing and validation behavior after the
fold guard.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/torch_onnx/torch_quant_to_onnx.py`:
- Around line 363-392: Update _finalize_resnet_quantizers so the int8/fp8 path
only accesses quantizer attributes when they exist, using safe getattr checks
for block.conv1, model.fc, model.global_pool, and related quantizers. Preserve
enabling/disabling behavior for present quantizers, and avoid AttributeError
when _prepare_resnet_quantizers skipped setup or a block variant lacks these
attributes.

In `@modelopt/onnx/export/fp8_exporter.py`:
- Line 34: Update the weight-channel threshold logic near the FP8
minimum-channel constant and the relevant convolution export check to account
for the convolution’s group count, matching the torch-side gate. Base the
decision on the effective total input channels rather than per-group
weight_input.values.shape[1], so grouped and depthwise convolutions consistently
enable or skip activation and weight Q/DQ paths.

In `@modelopt/onnx/utils.py`:
- Around line 1471-1481: The Cast-folding condition in the loop over
onnx_model.graph.node should not reject Cast-to-FLOAT nodes solely because
tensor_types lacks node.input[0]. Treat missing input type metadata as eligible
for folding, or resolve the producer output type before skipping; preserve the
existing FLOAT16 exclusion when the type is explicitly known.
- Around line 1524-1534: Replace the hard ValueError in the Q-consumer
validation with graceful skipping of that initializer, and emit a warning
identifying q_node.name and the observed scale dtype. Ensure
get_onnx_bytes_and_metadata continues exporting when a Q scale is a graph input
or remains non-FP16, while preserving FP16 conversion for valid Q scales.

---

Nitpick comments:
In `@examples/torch_onnx/torch_quant_to_onnx.py`:
- Around line 232-259: Update _calibrate_new_quantizers to track quantizers
based on their pre-existing _if_quant state rather than is_enabled when building
enabled_quantizers. Restore quantization only for quantizers whose _if_quant
state was originally active, preserving intentionally disabled quantizers
through the finally block.

In `@modelopt/torch/_deploy/utils/torch_onnx.py`:
- Line 48: Promote _convert_q_data_initializers_to_fp16 in
modelopt/onnx/utils.py to a public helper name, add that name to the module’s
__all__, and update the import and call sites in the ONNX export flow around
_convert_q_data_initializers_to_fp16 accordingly. Preserve the existing
sequencing and validation behavior after the fold guard.

In `@tests/examples/torch_onnx/test_torch_quant_to_onnx.py`:
- Around line 35-62: Harden _assert_residual_adds_are_quantized against missing
graph producers by replacing direct producers[...] accesses with
producers.get(...) and explicit assertions that identify the missing input or
node. Apply the same pattern to the MatMul/ReduceMean lookup paths around the
next(...) calls, asserting the expected producer exists before dereferencing it
while preserving the current validation behavior.
- Around line 64-116: Add concise comments immediately before each hard-coded
topology-count assertion in the quantization test, explaining how the expected
values derive from the ResNet-50 Q/DQ structure and mode-specific behavior.
Cover the activation quantizer counts, DQ fanout counts, and int8 weight
quantizer count, including details such as the number of convolution weights and
disabled fully connected weight quantization; leave the assertions unchanged.

In `@tests/unit/onnx/test_fold_casts.py`:
- Around line 86-97: In `_initializer_q_model`, replace the redundant
`input_dtype` and `output_dtype` calculations with one shared dtype variable
derived from `opset`, and use it when constructing the primary `y` output
instead of creating it as `FLOAT16` and mutating it afterward. Remove the
subsequent `outputs[0].type.tensor_type.elem_type` assignment while preserving
the existing opset-dependent dtype.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b4d1c1cd-df7c-44b1-be7d-a25ddb7f9ddb

📥 Commits

Reviewing files that changed from the base of the PR and between d2a78de and 77f2433.

📒 Files selected for processing (8)
  • CHANGELOG.rst
  • examples/torch_onnx/torch_quant_to_onnx.py
  • modelopt/onnx/export/fp8_exporter.py
  • modelopt/onnx/utils.py
  • modelopt/torch/_deploy/utils/torch_onnx.py
  • tests/examples/torch_onnx/test_torch_quant_to_onnx.py
  • tests/unit/onnx/quantization/test_fp8_mha_exporter.py
  • tests/unit/onnx/test_fold_casts.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.rst

Comment thread examples/torch_onnx/torch_quant_to_onnx.py Outdated
Comment thread modelopt/onnx/export/fp8_exporter.py Outdated
Comment thread modelopt/onnx/utils.py Outdated
Comment thread modelopt/onnx/utils.py Outdated
Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: ajrasane <131806219+ajrasane@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@modelopt/onnx/export/fp8_exporter.py`:
- Around line 196-197: Update the input-stem skip around the weight
dequantization pass so it applies only when the export is identified as a
ResNet, rather than using a three-channel input as the model-family
discriminator. Propagate and check an explicit ResNet/export marker alongside
node.inputs[0].name and weight_input.values.shape, while preserving the skip for
ResNet input stems and normal FP8 DQ restoration for non-ResNet models.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 22b23dca-030c-4c44-891d-67056a837b68

📥 Commits

Reviewing files that changed from the base of the PR and between 77f2433 and 7cf4bf5.

📒 Files selected for processing (4)
  • examples/torch_onnx/torch_quant_to_onnx.py
  • modelopt/onnx/export/fp8_exporter.py
  • tests/examples/torch_onnx/test_torch_quant_to_onnx.py
  • tests/unit/onnx/quantization/test_fp8_mha_exporter.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/examples/torch_onnx/test_torch_quant_to_onnx.py

Comment thread modelopt/onnx/export/fp8_exporter.py Outdated
ajrasane and others added 3 commits July 29, 2026 17:09
Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: ajrasane <131806219+ajrasane@users.noreply.github.com>
Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: ajrasane <131806219+ajrasane@users.noreply.github.com>
Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: ajrasane <131806219+ajrasane@users.noreply.github.com>
@ajrasane
ajrasane requested review from a team as code owners July 30, 2026 00:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

🧹 Nitpick comments (1)
modelopt/torch/quantization/plugins/timm.py (1)

16-25: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add __all__ for this module's public API.

Only is_resnet_quantization_supported is a non-underscore (public) symbol here; everything else is intentionally private. Declaring __all__ = ["is_resnet_quantization_supported"] makes that contract explicit and keeps from .timm import * in plugins/__init__.py predictable if more public helpers are added later.

As per coding guidelines, "Define each module's public API with __all__ = [...]."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modelopt/torch/quantization/plugins/timm.py` around lines 16 - 25, Add a
module-level __all__ declaration in timm.py containing only
is_resnet_quantization_supported, preserving the intended public API for
wildcard imports.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@modelopt_recipes/README.md`:
- Around line 79-84: Update the “Choosing where to look” guidance in README.md
to add a timm-specific lookup step before the general fallback, directing
readers to timm/<architecture>/ for architecture-specific recipes. Preserve the
existing huggingface and general guidance while ensuring timm recipes are
included in the selection flow.

---

Nitpick comments:
In `@modelopt/torch/quantization/plugins/timm.py`:
- Around line 16-25: Add a module-level __all__ declaration in timm.py
containing only is_resnet_quantization_supported, preserving the intended public
API for wildcard imports.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 62647b7a-3e4f-40ce-bedb-56b81fdb85f9

📥 Commits

Reviewing files that changed from the base of the PR and between 7cf4bf5 and edc43d4.

📒 Files selected for processing (22)
  • CHANGELOG.rst
  • examples/torch_onnx/README.md
  • examples/torch_onnx/torch_quant_to_onnx.py
  • modelopt/torch/opt/dynamic.py
  • modelopt/torch/quantization/algorithms.py
  • modelopt/torch/quantization/conversion.py
  • modelopt/torch/quantization/plugins/__init__.py
  • modelopt/torch/quantization/plugins/timm.py
  • modelopt_recipes/README.md
  • modelopt_recipes/timm/resnet/ptq/README.md
  • modelopt_recipes/timm/resnet/ptq/fp8.yaml
  • modelopt_recipes/timm/resnet/ptq/int8.yaml
  • modelopt_recipes/timm/resnet/ptq/mxfp8.yaml
  • modelopt_recipes/timm/resnet/ptq/nvfp4.yaml
  • modelopt_recipes/timm/resnet/ptq/nvfp4_awq_lite.yaml
  • modelopt_recipes/timm/resnet/ptq/static_fp8.quant_cfg.yaml
  • modelopt_recipes/timm/resnet/ptq/static_int8.quant_cfg.yaml
  • tests/unit/torch/nas/test_registry.py
  • tests/unit/torch/quantization/plugins/test_timm.py
  • tests/unit/torch/quantization/test_autoquant.py
  • tests/unit/torch/quantization/test_config_validation.py
  • tests/unit/torch/quantization/test_quantize_cpu.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/torch_onnx/README.md

Comment thread modelopt_recipes/README.md Outdated
Keep the change focused on shortcut QDQ placement with FP8, INT8, and AutoQuantize recipes.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: ajrasane <131806219+ajrasane@users.noreply.github.com>

@cjluo-nv cjluo-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot review (bedrock-claude-opus-5) — DM the bot to share feedback.

Re-review of the reworked (recipe-driven) implementation. The approach changed substantially since the last round, and most of the earlier findings are genuinely resolved:

Resolved

  • Duplicated calibration / second data pass (critical): the manual enable_calib → forward-loop → load_calib_amax block is gone. _add_resnet_residual_quantizers now runs as a CUSTOM_POST_CONVERSION_PLUGINS callback, i.e. after _replace_quant_module but before set_quantizer_by_cfg (see modelopt/torch/quantization/conversion.py::replace_quant_module), so the recipe's '*residual_quantizer' entry configures it and mtq.quantize's own forward loop calibrates it. The quantizers are also visible to print_quant_summary and modelopt state. Good fix.
  • Strict load_calib_amax() (critical): moot — standard calibration path is used now.
  • Auto-mode residual format inferred from the search space (critical): replaced by an explicit design — the residual quantizer is pinned to FP8 via fixed_quantization_config in the AutoQuantize recipe, documented in the README and PR body. Acceptable as an intentional choice (see one follow-up below).
  • Test asserted "every Add is residual" (minor): the test now selects residual adds structurally by their single Relu consumer before the count assertion.

Still open / new

  1. _disable_dead_quantizers still only inspects input_quantizer / output_quantizer / weight_quantizer, so a residual_quantizer that calibrates to amax == 0/NaN reaches the FP8 exporter's scale = 448 / amax — the exact case that guard was added for. The explicit NaN/non-positive check you described in the previous round no longer exists anywhere in the new code, so this half of the earlier comment regressed (one-line fix).
  2. tests/examples/torch_onnx/test_torch_quant_to_onnx.py runs resnet50 + auto with --trt_build, but the PR body says the AutoQuantize FP8/INT8 mix only builds on Blackwell+. Please confirm how this test is expected to pass on the CI GPUs.
  3. The recipe → mtq.auto_quantize kwargs translation duplicates examples/hf_ptq/hf_ptq.py::_mtq_inputs_from_auto_quantize_config, and the copy silently drops score_size (the new recipe sets score_size: 128 but the example uses --num_score_steps), cost_excluded_layers, and top-level candidate_formats.
  4. --auto_quantization_formats is silently overridden for ResNet in auto mode with no warning and no opt-out (--recipe is rejected with --quantize_mode=auto).
  5. Nits: the two new helpers are the only ones in this module without docstrings (the rationale docstring added in the previous round didn't survive the rewrite); "residual_quantizer" in block.downsample._modules could use hasattr; modelopt_recipes/README.md still doesn't mention the new top-level timm/ tree (earlier bot comment unaddressed); timm/resnet/ptq/{fp8,int8}.yaml are verbatim copies of configs/ptq/presets/model/{fp8,int8}.yaml plus one entry, so they will drift if the presets change.

No licensing concerns (new YAMLs carry the standard NVIDIA SPDX header); no prompt-injection content in the PR metadata.

Comment thread examples/torch_onnx/torch_quant_to_onnx.py
Comment thread examples/torch_onnx/torch_quant_to_onnx.py Outdated
Comment thread examples/torch_onnx/torch_quant_to_onnx.py Outdated
Comment thread tests/examples/torch_onnx/test_torch_quant_to_onnx.py Outdated
ajrasane and others added 2 commits August 7, 2026 15:03
Resolve the torch ONNX example conflicts while preserving the residual-only recipe scope.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: ajrasane <131806219+ajrasane@users.noreply.github.com>
Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: ajrasane <131806219+ajrasane@users.noreply.github.com>
@ajrasane
ajrasane force-pushed the ajrasane/quantize-resnet-residual-adds branch 3 times, most recently from b0b337f to 0a565bd Compare August 7, 2026 17:18

@cjluo-nv cjluo-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot review (bedrock-claude-opus-5) — DM the bot to share feedback.

All previously-flagged critical items are resolved (residual quantizers now go through the standard calibration flow via CUSTOM_POST_CONVERSION_PLUGINS, _disable_dead_quantizers covers residual_quantizer, residual adds are selected structurally in the test, --recipe is authoritative instead of silently overriding flags, and the AutoQuantize mapping no longer drops score_size/cost_excluded_layers/candidate_formats). Design-wise this reuses the existing recipe/preset/plugin machinery rather than inventing a new one — good.

Three things I'd like addressed before merge:

  1. _match_qformat can never match a recipe-derived config, so every recipe path silently skips the TRT post-processing. QUANT_CFG_CHOICES entries are model_dump(exclude_unset=True) (sparse: {"quantizer_name": "*", "enable": false}), while _match_qformat compares them against recipe.quantize.model_dump() (dense: parent_class: None, cfg: None, plus ~20 QuantizerAttributeConfig defaults). entry in config["quant_cfg"] is therefore always False, so _recipe_qformats() returns an empty set and _prepare_auto_quantize_format() never adds the Conv2d override. Concretely: --recipe=timm/resnet/ptq/fp8 never runs _disable_low_channel_conv_input_quantizers, i.e. the raw-RGB conv1 keeps its FP8 input quantizer — the exact Blackwell "no implementation for TRT_FP8QuantizeLinear" failure the helper exists to prevent. CI on Ada hides this. Same class of bug for recipe-supplied mxfp8/nvfp4 AutoQuantize candidates (no Conv2d→FP8 override, and _disable_high_rank_input_quantizers skipped).

  2. ResNet + auto is silently dropped from a previously-supported/tested configuration (README ✅ → blank, test pytest.skip("AutoQuantize is not supported for ResNet")). The flag-based auto path for resnet50 with --trt_build was passing before this PR; if the reason is the now-unconditional residual-quantizer registration, register the plugin only when residual quantization is actually requested, and otherwise please explain the regression in the PR body/CHANGELOG.

  3. New recipe YAMLs carry a truncated license header (2-line SPDX only) whereas every other modelopt_recipes/**.yaml uses the full LICENSE_HEADER Apache text. Not auto-enforced (the insert-license hook only covers python/shell/c), so it needs a manual fix.

Also: there is no unit coverage for _match_qformat / _recipe_qformats / _mtq_inputs_from_auto_quantize_config — a small pure-Python test asserting that the new timm/resnet/ptq/fp8.yaml resolves to {"fp8"} would have caught (1) immediately.

Comment thread examples/torch_onnx/torch_quant_to_onnx.py Outdated
Comment thread examples/torch_onnx/torch_quant_to_onnx.py Outdated
Comment thread examples/torch_onnx/torch_quant_to_onnx.py Outdated
Comment thread tests/examples/torch_onnx/test_torch_quant_to_onnx.py Outdated
Comment thread modelopt_recipes/timm/resnet/ptq/fp8.yaml
Comment thread modelopt_recipes/timm/resnet/ptq/int8.yaml
@gcunhase

gcunhase commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Thank you @ajrasane. Can you please add the runtime perf numbers for the Torch and ONNX quantization paths to confirm parity? Thanks!

@ajrasane
ajrasane force-pushed the ajrasane/quantize-resnet-residual-adds branch from 0a565bd to bfe06d9 Compare August 7, 2026 18:04
Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: ajrasane <131806219+ajrasane@users.noreply.github.com>
@ajrasane
ajrasane force-pushed the ajrasane/quantize-resnet-residual-adds branch from bfe06d9 to 291925d Compare August 7, 2026 18:08
@ajrasane

ajrasane commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

@gcunhase

Path Format GPU latency Throughput Engine size
torch_onnx FP8 0.292 ms 3,395 qps 25.05 MiB
onnx_ptq FP8 0.285 ms 3,476 qps 26.89 MiB
torch_onnx INT8 0.240 ms 4,122 qps 25.34 MiB
onnx_ptq INT8 0.232 ms 4,263 qps 27.09 MiB

Benchmarked on an NVIDIA RTX 6000 Ada Generation GPU with TensorRT 10.16.0.72.

Disclaimer: These are rough estimates and should not be considered official performance numbers, as the measurements may include some overheads.

🤖 Generated by Codex (AI agent).

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: ajrasane <131806219+ajrasane@users.noreply.github.com>

@cjluo-nv cjluo-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot review (bedrock-claude-opus-5) — DM the bot to share feedback.

Re-review (8 files, +448/-144). Design-review protocol applied first: this PR does not introduce a new subsystem — the residual quantizer is installed through the existing CUSTOM_POST_CONVERSION_PLUGINS hook (invoked by replace_quant_module before set_quantizer_by_cfg, so the recipe configures it and mtq.quantize's own forward loop calibrates it), and the new YAMLs slot into the existing modelopt_recipes/ catalog with $import units. That's reuse of the in-repo pattern, not a second one. Licensing is fine: the two new recipe YAMLs now carry the verbatim LICENSE_HEADER text. No prompt-injection content in the PR metadata.

Previous blockers — resolved

  • _match_qformat could never match a recipe-derived config, so --recipe silently skipped TRT post-processing: the reverse-preset-matching helpers are gone. _disable_low_channel_fp8_conv_input_quantizers and _disable_high_rank_input_quantizers now run unconditionally and decide from live quantizer state (num_bits == (4, 3), block_sizes), so --recipe=timm/resnet/ptq/fp8 does disable the raw-RGB conv1 input quantizer. Confirmed the loader converts eXmY → tuples, so the (4,3)/(2,1) comparisons are sound.
  • Truncated SPDX-only header on the new YAMLs: fixed, matches LICENSE_HEADER.
  • modelopt_recipes/README.md didn't mention the timm/ tree: fixed (catalog + selection guidance + placement), plus a timm/resnet/ptq/README.md documenting the delta.
  • AutoQuantize mapping dropped score_size/cost_excluded_layers/candidate_formats: all three are mapped now, and test_auto_quantize_recipe_mapping + a new e2e recipe test cover the top-level-candidates shape (previously the module_search_spaces=[] failure case).
  • Residual plugin registered unconditionally: now gated on the recipe actually enabling *residual_quantizer, and _disable_dead_quantizers covers residual_quantizer.

Also verified the removal of _calibrate_uncalibrated_quantizers is safe: forcing algorithm = "max" for mxfp8/nvfp4 calibrates the FP8 Conv overrides in the normal pass, and finish_stats_collection skips amax loading for dynamic/MX quantizers.

Why a nudge rather than an approve — see the reason field: the remaining items are product/behavior judgment calls plus a few small robustness gaps, not a re-flag of the fixed bugs.

@gcunhase

gcunhase commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

@gcunhase

Path Format GPU latency Throughput Engine size
torch_onnx FP8 0.292 ms 3,395 qps 25.05 MiB
onnx_ptq FP8 0.285 ms 3,476 qps 26.89 MiB
torch_onnx INT8 0.240 ms 4,122 qps 25.34 MiB
onnx_ptq INT8 0.232 ms 4,263 qps 27.09 MiB
Benchmarked on an NVIDIA RTX 6000 Ada Generation GPU with TensorRT 10.16.0.72.

Disclaimer: These are rough estimates and should not be considered official performance numbers, as the measurements may include some overheads.

🤖 Generated by Codex (AI agent).

Thanks @ajrasane , can you please also add the before and after this fix numbers so we can show the need of this PR? Thanks!

Comment thread examples/torch_onnx/README.md Outdated
if uses_fp8_conv_input:
_disable_low_channel_conv_input_quantizers(quantized_model)
if is_resnet:
_validate_resnet_quantizers(quantized_model)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this only needed for ResNet or any models that require QDQ nodes to be placed in the residual branch?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently I have validated this only for the ResNet models. Are there any other models with Residual connections we would like to target? I can try to find a more general solution using them.

@gcunhase

gcunhase commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Is there a way to make this behavior generic to other models with residual connections? Per my understanding, this logic would require each relevant model to have yaml files in model_recipes, is that correct?

@ajrasane

ajrasane commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Is there a way to make this behavior generic to other models with residual connections?

I can try to make this behavior generic for a few Convolutional models we would like to support. But I think it will be hard to guarantee that this would be applicable for all models with Residual connections as well. To do this, we will need to implement a graph parsing logic at the torch level to check for the residual connection pattern which is out of scope for the current PR.

Per my understanding, this logic would require each relevant model to have yaml files in model_recipes, is that correct?

Yes, the current presets are too generic. Users can get runnable quantized models with them, but they are not guaranteed to be the most performant. So it would be better to have per model configs that provide the best performance. Users can tweak their custom models by using these configs as guidance. This will also simplify a lot of code on the export and post-processing side where we inject/remove quantizers to give the best performance.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: ajrasane <131806219+ajrasane@users.noreply.github.com>
@ajrasane

ajrasane commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

@gcunhase

Format Path State GPU latency Throughput Engine size Change after fix
FP8 torch_onnx Before 0.292 ms 3,399 qps 25.04 MiB
FP8 torch_onnx After 0.292 ms 3,395 qps 25.05 MiB No measurable change
FP8 onnx_ptq Reference 0.285 ms 3,476 qps 26.89 MiB
INT8 torch_onnx Before 0.303 ms 3,274 qps 25.53 MiB
INT8 torch_onnx After 0.240 ms 4,122 qps 25.34 MiB 20.9% lower latency, 25.9% higher throughput
INT8 onnx_ptq Reference 0.232 ms 4,263 qps 27.09 MiB

Before uses --qformat without a ResNet recipe; After uses the corresponding timm/resnet/ptq recipe. Benchmarked on an NVIDIA RTX 6000 Ada Generation GPU with TensorRT 10.16.0.72.

Disclaimer: These are rough estimates and should not be considered official performance numbers, as the measurements may include some overheads.

🤖 Generated by Codex (AI agent).

@gcunhase

gcunhase commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

I can try to make this behavior generic for a few Convolutional models we would like to support. But I think it will be hard to guarantee that this would be applicable for all models with Residual connections as well. To do this, we will need to implement a graph parsing logic at the torch level to check for the residual connection pattern which is out of scope for the current PR.

Understood, is that something that we can try to add in a follow-up PR?

Here are some models that have residual connections that might be helpful to validate the more generic workflow:

# Model Residual pattern
1 ResNet Identity or Conv1×1 projection shortcut → Add
2 ResNeXt Same as ResNet but grouped convolutions in the main branch; same Add topology
3 Inception-ResNet-v1/v2 Inception module output + Conv1×1 projection of input → Add
4 SE-ResNet ResNet block + squeeze-excitation recalibration → Add on the residual path
5 MobileNetV2 Inverted residual: expand → depthwise → project Conv1×1 → Add (only when stride=1 and channels match)
6 EfficientNet MobileNetV2-style inverted residual + SE → Add; scaled uniformly
7 RegNet ResNet-style bottleneck with group convolutions → Add; design space parameterisation
8 RepVGG During training: Conv3×3 + Conv1×1 + identity → Add (all three branches collapsed to one conv at inference)
9 ConvNeXt Depthwise Conv7×7 → LayerNorm → Linear → GELU → Linear → Add (inverted bottleneck with depthwise residual)
10 YOLO v5/v8 CSP bottleneck: repeated Conv3×3 residual blocks with Add inside the cross-stage partial module

Thanks!

@gcunhase

gcunhase commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

@gcunhase

Format Path State GPU latency Throughput Engine size Change after fix
FP8 torch_onnx Before 0.292 ms 3,399 qps 25.04 MiB —
FP8 torch_onnx After 0.292 ms 3,395 qps 25.05 MiB No measurable change
FP8 onnx_ptq Reference 0.285 ms 3,476 qps 26.89 MiB —
INT8 torch_onnx Before 0.303 ms 3,274 qps 25.53 MiB —
INT8 torch_onnx After 0.240 ms 4,122 qps 25.34 MiB 20.9% lower latency, 25.9% higher throughput
INT8 onnx_ptq Reference 0.232 ms 4,263 qps 27.09 MiB —
Before uses --qformat without a ResNet recipe; After uses the corresponding timm/resnet/ptq recipe. Benchmarked on an NVIDIA RTX 6000 Ada Generation GPU with TensorRT 10.16.0.72.

Disclaimer: These are rough estimates and should not be considered official performance numbers, as the measurements may include some overheads.

🤖 Generated by Codex (AI agent).

Thank you! This is very clear in showing the benefits of this PR and parity with ONNX quant for INT8 while keeping FP8 performance.

@gcunhase gcunhase left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, let's move efforts to make this a more generic workflow to a follow-up PR. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants